package com.cmonbaby.hotfix;

import android.content.Context;
import android.util.Log;

import com.cmonbaby.hotfix.utils.ArrayUtils;
import com.cmonbaby.hotfix.utils.Constants;
import com.cmonbaby.hotfix.utils.ReflectUtils;

import java.io.File;
import java.util.HashSet;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * <pre>
 *     author  : Simon
 *     time    : 2019/1/22
 *     version : 1.0.0
 *     qq      : 8950764
 *     email   : simon@cmonbaby.com
 *     desc    : 热修复核心类
 * </pre>
 */
public class FixDexUtils {

    /**
     * 加载热修复Dex文件
     *
     * @param context 上下文
     */
    public static void loadFixedDex(Context context) {
        if (context == null) return;
        // Dex文件目录（私有目录中，存在之前已经复制过来的修复包）
        File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
        // 遍历私有目录中所有的文件
        File[] listFiles = fileDir.listFiles();
        if (listFiles == null) return;
        // 存放需要修复的dex集合，可能不止一个dex
        HashSet<File> loadedDex = new HashSet<>();
        for (File file : listFiles) {
            if (file.getName().endsWith(Constants.DEX_SUFFIX)) {
                // 找到修复包dex文件，加入集合
                loadedDex.add(file);
            }
        }
        // 模拟类加载器
        createDexClassLoader(context, fileDir, loadedDex);
    }

    /**
     * 创建加载补丁的DexClassLoader（自有）
     *
     * @param context   上下文
     * @param fileDir   Dex文件目录
     * @param loadedDex Dex文件集合
     */
    private static void createDexClassLoader(Context context, File fileDir, HashSet<File> loadedDex) {
        // 临时解压目录（并不是直接加载dex，而是先解压到该目录再加载）英 ['ɒptɪmaɪzd]
        String optimizedDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";
        // 创建该目录
        File fileOpt = new File(optimizedDir);
        // 不存该目录则创建
        if (!fileOpt.exists()) {
            // 创建多级目录
            boolean mkdirs = fileOpt.mkdirs();
            if (!mkdirs) {
                Log.e(FixDexUtils.class.getName(), "mkdirs false, please check the Android version!");
            }
        }
        for (File dex : loadedDex) {
            // 初始化DexClassLoader，参数：dex路径（修复包），解压目录，本地库，类加载器父类
            DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(),
                    optimizedDir, null, context.getClassLoader());
            // 每遍历一个要修复的dex文件，就插队一次
            hotfix(classLoader, context);
        }
    }

    /**
     * 热修复
     *
     * @param classLoader 加载了补丁的DexClassLoader（自有）
     * @param context     获取系统的PathClassLoader
     */
    private static void hotfix(DexClassLoader classLoader, Context context) {
        // 获取系统PathClassLoader
        PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader();
        try {
            // 获取自有的dexElements数组对象
            Object myDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(classLoader));
            // 获取系统的dexElements数组对象
            Object systemDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(pathLoader));
            // 合并成新的dexElements数组对象
            Object dexElements = ArrayUtils.combineArray(myDexElements, systemDexElements);
            // 获取系统的pathList对象
            Object systemPathList = ReflectUtils.getPathList(pathLoader);
            // 重新赋值系统的pathList属性值 --- 修改的dexElements数组对象
            ReflectUtils.setField(systemPathList, systemPathList.getClass(), dexElements);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
